# -*- coding: utf-8 -*-
import abc
import io
import tempfile
from io import StringIO
from typing import TYPE_CHECKING, Dict, List, Optional

import pandas as pd
import pywinauto.keyboard
import pywinauto
import pywinauto.clipboard

from jqktrader.log import logger
from jqktrader.utils.captcha import captcha_recognize
from jqktrader.utils.win_gui import ShowWindow, win32defines

import numpy as np
import time

if TYPE_CHECKING:
    # pylint: disable=unused-import
    from jqktrader import clienttrader


class IGridStrategy(abc.ABC):
    @abc.abstractmethod
    def get(self, control_id: int) -> List[Dict]:
        """
        获取 gird 数据并格式化返回

        :param control_id: grid 的 control id
        :return: grid 数据
        """
        pass

    @abc.abstractmethod
    def set_trader(self, trader: "clienttrader.IClientTrader"):
        pass


class BaseStrategy(IGridStrategy):
    def __init__(self):
        self._trader = None

    def set_trader(self, trader: "clienttrader.IClientTrader"):
        self._trader = trader

    @abc.abstractmethod
    def get(self, control_id: int) -> List[Dict]:
        """
        :param control_id: grid 的 control id
        :return: grid 数据
        """
        pass

    def _get_grid(self, control_id: int):
        grid = self._trader.main.child_window(
            control_id=control_id, class_name="CVirtualGridCtrl"
        )
        return grid

    def _set_foreground(self, grid=None):
        try:
            if grid is None:
                grid = self._trader.main
            if grid.has_style(win32defines.WS_MINIMIZE):  # if minimized
                ShowWindow(grid.wrapper_object(), 9)  # restore window state
            else:
                grid.wrapper_object().set_focus()
                # SetForegroundWindow(grid.wrapper_object())  # bring to front
        except:
            pass


class Copy(BaseStrategy):
    """
    通过复制 grid 内容到剪切板再读取来获取 grid 内容
    """

    _need_captcha_reg = False

    def get(self, control_id: int) -> List[Dict]:
        grid = self._get_grid(control_id)
        self._set_foreground(grid)
        pywinauto.clipboard.EmptyClipboard()  # 清空剪贴板避免旧数据干扰
        grid.type_keys("^A^C", set_foreground=False)
        logger.info("正在运行Copy方法")
        start_time = time.time()
        # 短轮询
        while time.time() - start_time < 0.1:  # 最多等待0.1秒，5次轮询
            if self._trader.app.top_window().child_window(class_name="Static", title_re="提示").exists(timeout=0.01):
                logger.info(f"短轮询{int((time.time() - start_time) * 1000)}ms，发现弹出的提示窗口")
                Copy._need_captcha_reg = True
                break
            time.sleep(0.02)  # 20ms轮询间隔
        if not Copy._need_captcha_reg:
            # 进入长轮询，总时长2s
            total_time = 0.8 # 总时间
            min_interval = 0.02 # 最短间隔时间
            num_intervals = 18 # 时间间隔个数
            std = total_time * 0.8 # 标准差，乘数区间为[0, 1]，数越大，间隔越均匀

            intervals = self._generate_intervals(total_time, min_interval, num_intervals, std)
            
            offset = 0
            for i in range(num_intervals):
                t = intervals[i] + offset
                t = t if t > 0 else 0
                time.sleep(t)
                t = time.time()-start_time
                if self._trader.app.top_window().child_window(class_name="Static", title_re="提示").exists(timeout=0.01):
                    logger.info(f"长轮询{int((t) * 1000)}ms，发现弹出的提示窗口")
                    Copy._need_captcha_reg = True
                    break
                offset = sum(intervals[:i+1])-(t)
                if t > total_time + 2:
                    break

        
        content = self._get_clipboard_data()
        return self._format_grid_data(content)

    def _check_captcha(self):
        if (
                self._trader.app.top_window().child_window(class_name="Static", title_re="提示").exists(timeout=1)
        ):
            logger.info("发现弹出的验证码窗口")
            return True
        return False

    def _format_grid_data(self, data: str) -> List[Dict]:
        try:
            df = pd.read_csv(
                io.StringIO(data),
                delimiter="\t",
                dtype=self._trader.config.GRID_DTYPE,
                na_filter=False,
            )
            return df.to_dict("records")
        except:
            Copy._need_captcha_reg = True

    def _get_clipboard_data(self) -> str:
        if Copy._need_captcha_reg:
            # if (
            #         self._trader.app.top_window().child_window(class_name="Static", title_re="提示").exists(timeout=1)
            # ):
            logger.info("处理验证码")
            file_path = "tmp.png"
            count = 5
            found = False
            while count > 0:
                self._trader.app.top_window().child_window(
                    control_id=0x965, class_name="Static"
                ).capture_as_image().save(
                    file_path
                )  # 保存验证码

                captcha_num = captcha_recognize(file_path).strip()  # 识别验证码
                captcha_num = "".join(captcha_num.split())
                logger.info("captcha result-->" + captcha_num)
                if len(captcha_num) == 4:
                    self._trader.app.top_window().child_window(
                        control_id=0x964, class_name="Edit"
                    ).set_focus()

                    pywinauto.keyboard.send_keys("{BKSP}{BKSP}{BKSP}{BKSP}")

                    pywinauto.keyboard.send_keys(captcha_num)

                    self._trader.app.top_window().set_focus()
                    pywinauto.keyboard.send_keys("{ENTER}")  # 模拟发送enter，点击确定

                    if (self._trader.app.top_window().child_window(class_name="Static", title_re="验证码").exists(timeout=1)):
                        logger.info("验证码识别错误")
                    else:
                        found = True
                        break
                    # try:
                    #     logger.info(
                    #         self._trader.app.top_window()
                    #             .child_window(control_id=0x966, class_name="Static")
                    #             .window_text()
                    #     )
                    # except Exception as ex:  # 窗体消失
                    #     logger.exception(ex)
                    #     found = True
                    #     break
                count -= 1
                self._trader.wait(0.1)
                self._trader.app.top_window().child_window(
                    control_id=0x965, class_name="Static"
                ).click()
            if not found:
                self._trader.app.top_window().Button2.click()  # 点击取消
            # else:
            #     Copy._need_captcha_reg = False
        
        count = 5
        while count > 0:
            try:
                return pywinauto.clipboard.GetData()
            # pylint: disable=broad-except
            except Exception as e:
                count -= 1
                logger.exception("%s, retry ......", e)

    # 生成轮询间隔时间的列表
    def _generate_intervals(self, total_time, min_interval, num_intervals, std):
        if num_intervals == 1:
            return [total_time]
        # 延长间隔
        def extend_gaps(a, n):
            new_a = []
            offset = 0
            for i in a:
                if i < n:
                    offset = offset + n - i
                    new_a.append(n)
                else:
                    new_a.append(i)
                if offset > 0 and new_a[-1] > n:
                    if offset > new_a[-1] - n:
                        offset = offset - new_a[-1] + n
                        new_a[-1] = n
                    else:
                        new_a[-1] = new_a[-1] - offset
                        offset = 0
            return new_a
        def normal_distribution(x, mean, std_dev):
            return np.exp(-.5 * np.power((x - mean) / std_dev, 2.))
        x = np.linspace(0, total_time*2, num_intervals)
        y = normal_distribution(x, total_time, std)
        # print(y)
        intervals = 1 / y
        intervals = intervals / np.sum(intervals) * (2 * total_time)
        # print('原始间隔时间', np.round(intervals, 3))
        min_time = min(intervals)
        if min_time >= min_interval:
            intervals[num_intervals//2] = min_interval
        else:
            a = intervals[num_intervals//2:]
            b = intervals[:num_intervals//2]
            b = b[::-1]
            a = extend_gaps(a, min_interval)
            b = extend_gaps(b, min_interval)
            intervals = np.concatenate((b[::-1], a))
        return intervals

class WMCopy(Copy):
    """
    通过复制 grid 内容到剪切板再读取来获取 grid 内容
    """

    def get(self, control_id: int) -> List[Dict]:
        grid = self._get_grid(control_id)
        grid.post_message(win32defines.WM_COMMAND, 0xE122, 0)
        self._trader.wait(0.1)
        logger.info("正在运行WMCopy方法")
        content = self._get_clipboard_data()
        return self._format_grid_data(content)


class Xls(BaseStrategy):
    """
    通过将 Grid 另存为 xls 文件再读取的方式获取 grid 内容
    """

    def __init__(self, tmp_folder: Optional[str] = None):
        """
        :param tmp_folder: 用于保持临时文件的文件夹
        """
        super().__init__()
        self.tmp_folder = tmp_folder

    def get(self, control_id: int) -> List[Dict]:
        grid = self._get_grid(control_id)

        # ctrl+s 保存 grid 内容为 xls 文件
        self._set_foreground(grid)  # setFocus buggy, instead of SetForegroundWindow
        grid.type_keys("^s", set_foreground=False)
        count = 10
        while count > 0:
            if self._trader.is_exist_pop_dialog():
                break
            self._trader.wait(0.2)
            count -= 1

        temp_path = tempfile.mktemp(suffix=".xls", dir=self.tmp_folder)
        self._set_foreground(self._trader.app.top_window())

        # alt+s保存，alt+y替换已存在的文件
        self._trader.app.top_window().Edit1.set_edit_text(temp_path)
        self._trader.wait(0.1)
        self._trader.app.top_window().type_keys("%{s}%{y}", set_foreground=False)
        # Wait until file save complete otherwise pandas can not find file
        self._trader.wait(0.2)
        if self._trader.is_exist_pop_dialog():
            self._trader.app.top_window().Button2.click()
            self._trader.wait(0.2)

        return self._format_grid_data(temp_path)

    def _format_grid_data(self, data: str) -> List[Dict]:
        with open(data, encoding="gbk", errors="replace") as f:
            content = f.read()

        df = pd.read_csv(
            StringIO(content),
            delimiter="\t",
            dtype=self._trader.config.GRID_DTYPE,
            na_filter=False,
        )
        return df.to_dict("records")
